/*
 * Licensed Materials - Property of tenxcloud.com
 * (C) Copyright 2018 TenxCloud. All Rights Reserved.
 * 2018-06-13  @author lizhen
 */

package utility

import (
	"errors"
	"reflect"
	"sync"
)

type Lazy interface {
	Value(value interface{}) (err error)
}

var (
	TypeMismatched = errors.New("type mismatched")
)

type lazy struct {
	lock  *sync.Once
	fetch func() (value interface{}, err error)
	value interface{}
	done  bool
}

func (l *lazy) Value(value interface{}) (err error) {
	if l.done {
		l.setValueTo(value)
		return
	}
	l.lock.Do(func() {
		if l.value, err = l.fetch(); err != nil {
			l.lock = new(sync.Once)
			return
		} else {
			l.setValueTo(value)
			l.done = true
		}
	})
	return
}

func (l lazy) setValueTo(receiver interface{}) (err error) {
	rv := reflect.Indirect(reflect.ValueOf(receiver))
	vv := reflect.ValueOf(l.value)
	if vv.Type().AssignableTo(rv.Type()) && rv.CanSet() {
		rv.Set(reflect.ValueOf(l.value))
	}
	return TypeMismatched
}

func NewLazy(fetch func() (value interface{}, err error)) Lazy {
	return &lazy{
		lock:  new(sync.Once),
		fetch: fetch,
	}
}
